package lockRelate

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"sync"
)

/*
	分片读写锁（MapRwLock），用于在并发场景下对多个分片（Shard）进行细粒度的读写控制。
	通过哈希函数将不同的键（key）映射到不同的分片锁上，从而减少锁竞争，提高并发性能
	应用场景：
	1.分片锁的作用：通过将数据分片并为每个分片分配独立的锁，减少锁竞争，提高并发性能。
	2.哈希函数的作用：将 key 均匀分布到不同的分片锁上，避免热点问题。
	3.适用场景：适用于需要高并发访问共享数据的场景，如缓存、分布式存储等
*/

// SharkRwLock 分片读写锁
type SharkRwLock struct {
	// 继承RWMutex,提供的读写功能
	sync.RWMutex
}

// MapRwLock map读写锁
type MapRwLock struct {
	ShardCount int            // 分片数量，即有多少个分片锁
	RwLocks    []*SharkRwLock // 分片锁的切片，每个分片对应一个锁
}

// fnv32 哈希函数
func fnv32(key string) uint32 {
	// 著名的fnv哈希函数,由Glenn、Fowler、Landon Curt Noll和Kiem-Phong Vo 创建
	hash := uint32(2166136261)
	const prime32 = uint32(16777619)
	keyLength := len(key)
	for i := 0; i < keyLength; i++ {
		hash *= prime32
		hash ^= uint32(key[i])
	}
	return hash
}

// GetShardLock 获取分片锁
func (m *MapRwLock) GetShardLock(keys ...interface{}) (res *SharkRwLock) {
	if len(keys) == 1 {
		// 判断key的类型
		dataType := reflect.TypeOf(keys[0])
		switch dataType.Kind() {
		case reflect.Int:
			res = m.RwLocks[uint(keys[0].(int))%uint(m.ShardCount)]
		case reflect.Int64:
			res = m.RwLocks[uint64(keys[0].(int64))%uint64(m.ShardCount)]
		case reflect.String:
			res = m.RwLocks[uint(fnv32(keys[0].(string)))%uint(m.ShardCount)]
		default:
			// 默认使用第一个分片锁
			res = m.RwLocks[0]
		}
	} else {
		// 如果有多个key,将它们拼接成一个字符串然后计算哈希值
		var b bytes.Buffer
		for _, key := range keys {
			dataType := reflect.TypeOf(key)
			switch dataType.Kind() {
			case reflect.Int:
				b.WriteString(strconv.Itoa(key.(int)))
			case reflect.Int64:
				b.WriteString(strconv.FormatInt(key.(int64), 10))
			case reflect.String:
				b.WriteString(key.(string))
			default:
				fmt.Println("unhandled default case")
			}
			b.WriteString("#")
		}
		res = m.RwLocks[uint(fnv32(b.String()))%uint(m.ShardCount)]
	}
	return
}

// NewMapRwLock 实例化map读写锁
func NewMapRwLock(shardCount int) *MapRwLock {
	ins := &MapRwLock{
		ShardCount: shardCount,
	}
	for i := 0; i < shardCount; i++ {
		ins.RwLocks = append(ins.RwLocks, &SharkRwLock{})
	}
	return ins
}
